//
//  NovaBaseBarChartView.swift
//  Demo
//
//  Created by 李然 on 2023/3/6.
//

import UIKit
import SwiftDate
import CoreGraphics
import RxSwift
import RxCocoa

public protocol NovaXAxisValueFormatter: NSObjectProtocol {
    
    /// x轴的文字
    func string(at index: Int) -> String
    
    /// index的Bar和后面的Bar中间画一条虚线
    func isDottedLineShow(at index: Int) -> Bool
}

extension NovaXAxisValueFormatter {
    
    func isDottedLineShow(at index: Int) -> Bool {
        return false
    }
}

public protocol NovaYAxisValueFormatter: NSObjectProtocol {
    
    /// Y轴的文字
    func string(at index: Double) -> String
    
    /// 是否画当前 Index 下的横向虚线
    func isDottedLineShow(at index: Double) -> Bool
}

extension NovaYAxisValueFormatter {
    
    func isDottedLineShow(at index: Double) -> Bool {
        return true
    }
}

open class NovaBaseChartData {
    
}

public class ChartLimitLine {
    public var limit: Double = 0.0
    public var color = UIColor.clear
    public var lineDashPhase = CGFloat(0.0)
    public var lineDashLengths: [CGFloat]?
    public var labelText: String = ""
    public var font: UIFont = UIFont.systemFont(ofSize: 11)
}

open class NovaBaseChartView: View {
    
    public let selectIndex = PublishSubject<Int>()
    public let gestureEnd = PublishSubject<Void>()
    
    /// Y轴数据
    public var maxYAxisValue: Double = 0
    public var minYAxisValue: Double = 0
    public var yAxisLabelCount: Int = 0
    public var yValueFormatter: NovaYAxisValueFormatter?
    
    /// x轴数据
    public var data: [NovaBaseChartData] = [] {
        didSet {
            self.setNeedsDisplay()
        }
    }
    public var xValueFormatter: NovaXAxisValueFormatter?
    
    /// 柱状图第一个和最后一个跟表框的间距
    public var barStartAndEndMargin: CGFloat = 0
    
    /// 网格线颜色
    public var gridLineColor: UIColor = .lightGray
    
    /// 轴文字颜色
    public var axisLabelColor: UIColor = .lightGray
    
    /// 柱状图和之间空隙的比例
    public var barRatio: CGFloat = 1.0
    
    /// Marker
    public var marker: Markerable?
    
    /// MarkerIndex
    public var markerIndex: Int? {
        didSet {
            setNeedsDisplay()
        }
    }
    
    /// 柱子圆角
    public var barCornerRadius: CGFloat = 1
    
    /// 是否显示右轴文字
    public var showRightAxisLabel = true
    
    public var labelAttr: [NSAttributedString.Key: Any] {
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineSpacing = 6
        return [.foregroundColor: axisLabelColor,
                .font: UIFont.systemFont(ofSize: 10),
                .paragraphStyle: paragraphStyle]
    }
    
    public private(set) var chartFrame: CGRect = .zero
    public private(set) var xAxisLabelWidth: CGFloat = 0
    public private(set) var xAxisLabelHeight: CGFloat = 0
    public private(set) var yAxisLabelWidth: CGFloat = 0
    public private(set) var yAxisLabelHeight: CGFloat = 0
    // label标签和图表框之间的间距
    private let labelMargin: CGFloat = 5
    
    lazy var sliderLineView: NovaChartLineView = {
        let view = NovaChartLineView()
        view.isHidden = true
        self.addSubview(view)
        return view
    }()
    
    public private(set)  lazy var generator: UIImpactFeedbackGenerator = {
        let generator = UIImpactFeedbackGenerator(style: .light)
        return generator
    }()
    
    public var barRects: [CGRect] = []
    public private(set) var _index: Int?
    
    private var _limitLines: [ChartLimitLine] = []
    
    public func clear() {
        self.data = []
        self.setNeedsDisplay()
    }
    
    open override func makeUI() {
        super.makeUI()
        
        self.backgroundColor = .clear
        
        self.isUserInteractionEnabled = false
        
        let pan = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized(_:)))
        self.addGestureRecognizer(pan)
        let tap = UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:)))
        self.addGestureRecognizer(tap)
        
        self.gestureEnd
            .debounce(RxTimeInterval.seconds(3), scheduler: MainScheduler.instance)
            .subscribe(onNext: { [weak self] _ in
                guard let `self` = self else { return }
                self._index = nil
                self.sliderLineView.isHidden = true
            }).disposed(by: rx.disposeBag)
    }
    
    public func removeAllLimitLines() {
        _limitLines.removeAll()
        setNeedsDisplay()
    }
    
    public func addLimitLine(_ line: ChartLimitLine) {
        _limitLines.append(line)
        setNeedsDisplay()
    }
    
    open override func draw(_ rect: CGRect) {
        super.draw(rect)
    
        self.barRects.removeAll()
        
        // 算y轴的文字高度和长度
        if maxYAxisValue > minYAxisValue, self.yAxisLabelCount > 1, showRightAxisLabel {
            let yCount = self.yAxisLabelCount
            let yValueMargin = (maxYAxisValue - minYAxisValue)/(Double(yCount) - 1)
            for index in 0..<yCount {
                let yValue = minYAxisValue + yValueMargin * CGFloat(index)
                if let yStringValue = self.yValueFormatter?.string(at: yValue) {
                    let size = yStringValue.size(withAttributes: labelAttr)
                    yAxisLabelWidth = max(yAxisLabelWidth, size.width)
                    yAxisLabelHeight = max(yAxisLabelHeight, size.height)
                }
            }
        }
        
        // 算x轴的文字长度和高度
        if self.data.count > 0 {
            for index in 0..<self.data.count {
                if let xStringValue = xValueFormatter?.string(at: index) {
                    let size = xStringValue.size(withAttributes: labelAttr)
                    xAxisLabelWidth = max(xAxisLabelWidth, size.width)
                    xAxisLabelHeight = max(xAxisLabelHeight, size.height)
                }
            }
        }
        
        // 算图表 frame
        do {
            let widthEmpty = max(labelMargin + yAxisLabelWidth, xAxisLabelWidth / 2)
            let heightEmpty = yAxisLabelHeight / 2 + labelMargin + xAxisLabelHeight
            let chartWidth = rect.width - widthEmpty
            let chartHeight = rect.height - heightEmpty
            
            chartFrame = CGRect(x: widthEmpty,
                                y: yAxisLabelHeight / 2,
                                width: chartWidth,
                                height: chartHeight)
        }
        
        drawYAxisValuesAndFullLines(rect)
        
        drawXAxisValuesAndFullLines(rect)
    }
    
    open func drawXAxisValuesAndFullLines(_ rect: CGRect) {
        let xCount = self.data.count
        guard xCount > 0 else { return }
        
        guard let context = UIGraphicsGetCurrentContext() else { return }
        
        context.saveGState()
        defer { context.restoreGState() }
        
        var x: CGFloat = barStartAndEndMargin + chartFrame.minX
        // w * c + (c - 1) * r * w = TotalWidth
        let barWidth = (chartFrame.width - 2 * barStartAndEndMargin) / (CGFloat(xCount) * (barRatio + 1 ) - barRatio)
        
        for index in 0..<self.data.count {
            if let xValueFormatter = xValueFormatter {
                let xValue = xValueFormatter.string(at: index)
                if !xValue.isEmpty {
                    context.drawText(xValue, at: CGPoint(x: x + barWidth / 2, y: chartFrame.maxY + labelMargin), align: .center, attributes: labelAttr)
                }
                
                if xValueFormatter.isDottedLineShow(at: index) {
                    if index != 0 {
                        context.setStrokeColor(gridLineColor.cgColor)
                        let dottieX = x - barRatio * barWidth / 2
                        let dottieLinePath = UIBezierPath()
                        dottieLinePath.lineWidth = 0.5
                        dottieLinePath.setLineDash([1.0, 1.0], count: 1, phase: 0)
                        dottieLinePath.move(to: CGPoint(x: dottieX, y: chartFrame.minY))
                        dottieLinePath.addLine(to: CGPoint(x: dottieX, y: chartFrame.maxY))
                        dottieLinePath.stroke()
                    }
                }
            }
            
            let saveRect = CGRect(x: x, y: chartFrame.minY, width: barWidth, height: chartFrame.height)
            self.barRects.append(saveRect)
            
            x += barWidth
            x += barWidth * barRatio
        }
    }

    /// 画Y轴文字和实线
    open func drawYAxisValuesAndFullLines(_ rect: CGRect) {
        let yCount = self.yAxisLabelCount
        guard yCount > 1, maxYAxisValue > minYAxisValue else { return }
        
        guard let context = UIGraphicsGetCurrentContext() else { return }
        
        context.saveGState()
        defer { context.restoreGState() }
        
        let path = UIBezierPath()
        path.lineWidth = 0.5
        path.setLineDash([1.0, 1.0], count: 1, phase: 0)
        let yValueMargin = (maxYAxisValue - minYAxisValue)/(Double(yCount) - 1)
        for index in 0..<yCount {
            
            let yValue = minYAxisValue + yValueMargin * CGFloat(index)
            
            let y = (1 - (yValue - minYAxisValue) / (maxYAxisValue - minYAxisValue)) * chartFrame.height + chartFrame.minY
            
            if let yValueFormatter = yValueFormatter, yValueFormatter.isDottedLineShow(at: yValue) {
                if yValue == 0{
                    let start = CGPoint(x: 0, y: y)
                    let end = CGPoint(x: chartFrame.maxX, y: y)
                    path.move(to: start)
                    path.addLine(to: end)
                } else {
                    let start = CGPoint(x: chartFrame.minX, y: y)
                    let end = CGPoint(x: chartFrame.maxX, y: y)
                    path.move(to: start)
                    path.addLine(to: end)
                }
            }
            
            if let yStringValue = self.yValueFormatter?.string(at: yValue), showRightAxisLabel {
                context.drawText(yStringValue, at: CGPoint(x: yAxisLabelWidth / 2,
                                                      y: y - yAxisLabelHeight / 2), align: .center, attributes: labelAttr)
            }
        }
        
        context.setStrokeColor(gridLineColor.cgColor)
        path.stroke()
        
        for line in _limitLines {
            let value = line.limit
            if value > minYAxisValue, value < maxYAxisValue {
                let path = UIBezierPath()
                path.lineWidth = 0.5
                path.setLineDash(line.lineDashLengths, count: 1, phase: line.lineDashPhase)
                
                let y = (1 - (value - minYAxisValue) / (maxYAxisValue - minYAxisValue)) * chartFrame.height + chartFrame.minY
                let start = CGPoint(x: chartFrame.minX, y: y)
                let end = CGPoint(x: chartFrame.maxX, y: y)
                path.move(to: start)
                path.addLine(to: end)
                context.setStrokeColor(line.color.cgColor)
                path.stroke()
                
                if line.labelText.count > 0 {
                    let paragraphStyle = NSMutableParagraphStyle()
                    paragraphStyle.lineSpacing = 6
                    let attr: [NSAttributedString.Key: Any] = [.foregroundColor: line.color,
                                                               .font: line.font,
                                                               .paragraphStyle: paragraphStyle]
                    let labelSize = line.labelText.size(withAttributes: attr)
                    context.drawText(line.labelText, at: CGPoint(x: max(yAxisLabelWidth, labelSize.width) / 2,
                                                                 y: y - labelSize.height / 2), align: .center, attributes: attr)
                }
            }
        }
    }
}

// MARK: - 自定义方法
extension NovaBaseChartView {
    
    @objc private func panGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
        if (recognizer.state == .began || recognizer.state == .changed ) && recognizer.numberOfTouches > 0 {
            let location = recognizer.location(in: self)
            let frame = CGRect(x: 0, y: 0, width: chartFrame.width, height: self.bounds.height)
            guard frame.contains(location) else {
                return
            }
            self.setTouchLocation(location, inView: self)
        }
        if recognizer.state == .ended {
            gestureEnd.onNext(())
        }
    }
    
    @objc private func tapGestureRecognized(_ recognizer: UITapGestureRecognizer) {
        let location = recognizer.location(in: self)
        self.setTouchLocation(location, inView: self)
        
        if recognizer.state == .ended {
            gestureEnd.onNext(())
        }
    }
    
    public func setTouchLocation(_ point: CGPoint, inView view: UIView) {
        self.sliderLineView.isHidden = false
        self.sliderLineView.frame = CGRect(x: point.x, y: chartFrame.minY, width: 1, height: chartFrame.height)
        for (index, barRect) in self.barRects.enumerated() where barRect.contains(point) {
            guard _index != index else {
                return
            }
            _index = index
            selectIndex.onNext(index)
            self.generator.impactOccurred()
            break
        }
    }
}

class NovaChartLineView: View {
    
    override func makeUI() {
        super.makeUI()
        self.backgroundColor = .clear
    }
    
    override func draw(_ rect: CGRect) {
        
        guard let context = UIGraphicsGetCurrentContext() else { return }
        context.saveGState()
        defer { context.restoreGState() }
        
        context.setStrokeColor(UIColor.black.cgColor)
        
        let dottieLinePath = UIBezierPath()
        dottieLinePath.lineWidth = 1
        dottieLinePath.setLineDash([2.0, 2.0], count: 2, phase: 0)
        dottieLinePath.move(to: CGPoint(x: 0.5, y: 0))
        dottieLinePath.addLine(to: CGPoint(x: 0.5, y: rect.height))
        dottieLinePath.stroke()
    }
}
